一路上感謝各位讀者們的支持和回饋。
本 30 天系列文目前已經將篇幅重新整理、編纂成冊。
《JavaScript 概念三明治》在天瓏書局上架囉!
喜歡這個系列,想閱讀更詳細原理說明的讀者可以參考:
https://www.tenlong.com.tw/products/9789864347575
上一篇針對 Promise 的語法做了一個基本的解說,但其實今天的內容才是我想講的,Promise 的運作邏輯不難理解,但若是 Promise 在整個 JS 以及瀏覽器裡的流程可能就比較複雜了,現在我們都知道幾件事情:
而雖然在上一章節一直提到非同步,但是對於 Promise 裡所謂非同步執行的部分,目前我們還是沒有很明確的解釋,到底是哪一部分會以非同步的方式被執行?以及什麼時候會執行?這是這篇文章想要探討跟說明的。
我們在 Event Queue 章節裡面所提到 Web API 有些具有非同步的行為,而在非同步的目的達成之後,瀏覽器會把給定的對應的函式推送到 Event Queue 裡面,這些一個一個函式正好代表每一件要做的事情,因此在 JS 裡面,以「 Task 」或 「Macrotask 」來稱呼,為了避免混淆,以下將用 Macrotask Queue 來指稱之前提到的 Event Queue 。
關於 Task 有兩個細節可以注意:
Microtask 通常由 Promise 產生,Promise 裡用到的 .then / .catch 函式會以非同步的方式來被執行,回想下 Queue 的概念,所以的非同步行為指的是,會在全域執行環境執行完之後才被執行,因此一但 Promise 的 callback 內容執行完成,狀態再也不是 pending 時,.then 或 .catch 的函式內容就會被推送到 Queue 裡面等待執行,這個被推送到 Queue 的函式就是 Microtask。
相對於管理 Web API 所屬事件的 Macrotask Queue ,Promise 產生的 Microtask 也有自己的 Queue ,在 JS 內被稱為 Job Queue 或 Microtask Queue,而 Job Queue 與 Event Queue 運作方式上有一點不一樣。
差在哪裡呢?在 Queue 裡面的每個 Macrotask 執行完畢後 ,就算 Event Queue 裡面還有其他的 Task,JS 引擎依舊會優先執行 Microtask Queue 裡面的所有 Task ,在這個同時也不會重新渲染網頁,換句話說,Microtask 的執行是穿插在每個 Macrotask 之間,兩者的差異也就在執行順序的不同而已。
如果還是覺得很抽象,下面我會帶個例子,直接用程式碼來比較 Macrotask 與 Microtask 執行順序的不同,應該比較能夠讓你了解,看看下面的程式碼:
setTimeout(() => alert("timeout"));
Promise.resolve()
.then(() => alert("promise"));
alert("global ex. context");
這段程式碼剛好同時用到 Web API 與 Promise ,各自在呼叫後會產生一個 Macrotask 以及 Microtask ,不過在順序上是哪個會先被執行呢?稍微分析一下:
因此推測 alert 的順序應該會像是這樣:
"global ex. contenxt"
"timeout"
"promise"
但是並不是!結果會是 "promise"
比 "timeout"
還要更先被 log 出來:
"global ex. contenxt"
"promise"
"timeout"
這是為什麼呢?這邊可能會有點抽象,前面我們在分析 JS 語法與運作模式的時候,大多是從 JS 引擎的角度出發。而前面也有提到, Queue 的概念並不屬於 JS 引擎的一部分,相對的歸屬於瀏覽器。對於瀏覽器來說,在網頁頁面開啟時,載入對應的 JS 檔並且執行這件事情,也是一個 Macrotask 。
而剛剛提到 Macrotask 執行完畢後,會優先執行 Microtask ,因此你會看到 "promise"
出現的順序先於 "timeout"
。
setTimeout(() => alert("timeout"));
Promise.resolve()
.then(() => alert("promise"));
alert("global ex. context");
這個執行的結果有筆誤打錯嗎?
Hi, Nono :
很抱歉是我筆誤了,這邊是想表達在渲染完之後會先執行 MicroTask ,所以 Promise 的內容會先被執行!非常感謝你的回復,看來我整個系列寫完之後要再仔細校稿一次...
最近也剛好在研究 event loop 相關的資料。
普遍找到的資料都是說再一次 event loop 循環中
2.
到步驟 3.
execution context stack
應該要是空的,才執行 microtask3.
執行到 microtask queue 為空為止依照上面的模型來跑
setTimeout(() => alert("timeout"));
Promise.resolve()
.then(() => alert("promise"));
alert("global ex. context");
setTimeout
,因為發現是 delay time 為 0,所以把他的 callback 直接放入 task queue 中Promise.resolve()
會產生 microtask,放入 microtask queue 中execution context stack
也為空,執行 promise
的內容setTimeout 的 callback
感覺和作者畫的最後那張圖不太一樣
以上是個人最近研究的一些心得,有任何錯誤請見諒
附上一些參考資料
asks, microtasks, queues and schedules
whatwg - Event loop processing model
Hi , Nono :
基本上我的理解跟你寫的五步驟是差不多的,所以只要了解幾個重點:
At the execution of any JS file, the JS engine wraps the contents in a function and associates the function with an event either start or launch. The JS engine emits the start event, the events are added to the task queue (as a macrotask).
而為什麼你會覺得有問題的差別大概就在第三點吧,因為我想強調這點,才把它放進堆疊圖內,但是可能 Event Loop 的形狀太像是迴圈了,可能因此容易誤導執行順序,不知道你是不是因為這樣才覺得疑惑,如果是的話,我到時候可能要回頭重新想一下怎麼呈現這裡的概念模型,先感謝你的討論!
Hi, Mooji:
非常感謝你的回覆!
那這樣我覺得應該是圖片上的理解有點會讓人誤會
附上之前找到的一張還不錯的圖給你做參考
event loop
你提供的這篇文章滿有趣的還自己用 js 做範例!
最後能偷偷問一下你的圖怎麼做的嗎XD
感謝你百忙之中回覆我
鐵人賽辛苦啦!剩沒幾天了~加油!
Hi Nono:
抱歉剛剛發現之前好像忘記回你XD
我那個時候是用 Sketch 來畫的,
雖來他原來的功能是用來做UI介面的,
但是我發現也很適合用來畫概念圖,就拿來試試看了,
看來成效還不錯!
Hi Nono,請問4. 此 task 執行完畢是指什麼task? 模型 step2的執行 macrotask好像底下沒有跑到?
Hi 您好
最近我在研究Event Loop 的機制
但有一點不太明白,
很多文件說明在Event Loop的第一步是:
"從macrotask queue裡面取出oldest task放到event loop內執行"
然後要在這個task執行完後 才會將所有的microtask 執行完畢
最後再將其他macrotask執行完畢
但是我目前測試的結果 都會是先印出microtask的結果 再印出剩下的macrotask的結果
例如這個測試 https://jsfiddle.net/9k153st8/
這樣跟上面的流程好像有點不太一樣
不知道是哪個地方我有認知錯誤
還是哪個流程有誤
能否麻煩您在幫我指正一下
謝謝
嗨ceall8650:
你這個問題我也有疑惑過,
後來我找到的一個比較普遍的解釋方式是,
對瀏覽器內的 Event Loop 來說,第一次載入 JS 來執行這個動作,也是一個 MacroTask ,所以在全域環境的所有堆疊結束之後,它會直接執行 MicoTask 裡面的 Task。
不知道這樣有沒有解答到你的問題?有什麼新的結論也可以再跟我說!
Mooji 感謝您的回覆與分享
我有再看了一下, 應該是原本的JS也算是external script 的一種沒錯, 所以算是macrotask.
而之前搞混的原因主要是認為 "microtask會比macrotask執行" 的原因, 所以在event loop 時, macrotask應該是最後一個步驟. 但我認為應該是microtask 是最後一個步驟, 如果microtask執行完後 macrotask queue仍有task, 應該是開始下一個loop. 也就符合Event Loop 執行的順序
以上是我的看法, 看這樣理解你覺得是否合理
你說的最後一個步驟是什麼意思?應該沒有所謂真正的最後一個步驟,只有誰會比誰會先被執行的問題,基本上搞清楚這個順序應該就可以解答你的問題
恩...我這邊的步驟指的是每一次Loop的時候, 會做的哪些步驟.
我是這邊是參考這兩個的地方來幫助我理解
https://javascript.info/event-loop#summary
https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
我覺得在定義"loop"的時候應該也會有它的意義, 所以我是認為應該會有"第一步"跟"最後一步", 然後再重新回到第一步, 形成一個loop. 或許這樣比較符合這樣的意思.
以上是我自己的解讀跟看法
感謝您撥空回覆